﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;

namespace DDD {
    /// <summary>
    /// <see cref="FrameBuffer"/>クラスに拡張メソッドを追加するクラス
    /// </summary>
    public static class FrameBufferExtensions {

        /// <summary>
        /// コンパイル
        /// </summary>
        /// <param name="fbuf"><see cref="FrameBuffer"/>オブジェクト</param>
        public static void Compile (this FrameBuffer fbuf) {
            var cache = Graphics3DExtensions.GetGLCache ();
            var id = cache[fbuf];
            if (id > 0) {
                return;
            }
            ErrorCode err;

            GL.GenFramebuffers (1, out id);
            if ((err = GL.GetError ()) > 0) {
                throw new InvalidOperationException ("glGenFrameBuffers was failed, err=" + err);
            }

            GL.BindFramebuffer (FramebufferTarget.DrawFramebuffer, id);
            if ((err = GL.GetError ()) > 0) {
                throw new InvalidOperationException ("glBindFrameBuffer was failed, err=" + err);
            }

            var depth = fbuf.DepthBuffer;
            if (depth != null) {
                depth.Compile ();
                GL.FramebufferTexture (FramebufferTarget.DrawFramebuffer, FramebufferAttachment.DepthAttachment, cache[depth], 0);
                if ((err = GL.GetError ()) > 0) {
                    throw new InvalidOperationException ("glFramebufferTexture was failed, err=" + err);
                }
            }

            var bufs = new DrawBuffersEnum[fbuf.ColorBufferCount];
            for (var i = 0; i < fbuf.ColorBufferCount; i++) {
                var color = fbuf.GetColorBuffer (i);
                if (color != null) {
                    color.Compile ();
                    GL.FramebufferTexture (FramebufferTarget.DrawFramebuffer, FramebufferAttachment.ColorAttachment0 + i, cache[color], 0);
                    if ((err = GL.GetError ()) > 0) {
                        throw new InvalidOperationException ("glFramebufferTexture was failed, err=" + err);
                    }
                    bufs[i] = DrawBuffersEnum.ColorAttachment0 + i;
                }
            }

            GL.DrawBuffers (fbuf.ColorBufferCount, bufs);
            if ((err = GL.GetError ()) > 0) {
                throw new InvalidOperationException ("glDrawBuffers was failed, err=" + err);
            }

            GL.BindFramebuffer (FramebufferTarget.DrawFramebuffer, 0);
            cache.Add (fbuf, id);

            fbuf.Validate ();
        }

        /// <summary>
        /// レンダリング
        /// </summary>
        /// <param name="fbuf"><see cref="FrameBuffer"/>オブジェクト</param>
        public static void Render (this FrameBuffer fbuf) {
            var cache = Graphics3DExtensions.GetGLCache ();
            var id = cache[fbuf];
            if (id == 0) {
                throw new InvalidOperationException ("OpenGL id was not found.");
            }

            GL.BindFramebuffer (FramebufferTarget.DrawFramebuffer, id);

            // 初期値でクリア
            foreach (var cbuf in fbuf.ColorBuffers) {
                var i = fbuf.GetColorBufferIndex (cbuf);
                if (fbuf.GetColorClear (i)) {
                    switch (cbuf.PixelFormat) {
                        case PixelFormat.R8: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<float>)cbuf).ClearColor); break;
                        case PixelFormat.R8i: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<sbyte>)cbuf).ClearColor.Select(x=>(int)x).ToArray ()); break;
                        case PixelFormat.R8ui: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<byte>)cbuf).ClearColor.Select(x=>(uint)x).ToArray ()); break;
                        case PixelFormat.R16: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<float>)cbuf).ClearColor); break;
                        case PixelFormat.R16f: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<float>)cbuf).ClearColor); break;
                        case PixelFormat.R16i: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<short>)cbuf).ClearColor.Select(x=>(int)x).ToArray ()); break;
                        case PixelFormat.R16ui: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<ushort>)cbuf).ClearColor.Select(x=>(uint)x).ToArray ()); break;
                        case PixelFormat.R32f: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<float>)cbuf).ClearColor); break;
                        case PixelFormat.R32i: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<int>)cbuf).ClearColor); break;
                        case PixelFormat.R32ui: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<uint>)cbuf).ClearColor); break;

                        case PixelFormat.RG8: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<float>)cbuf).ClearColor); break;
                        case PixelFormat.RG8i: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<sbyte>)cbuf).ClearColor.Select(x=>(int)x).ToArray ()); break;
                        case PixelFormat.RG8ui: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<byte>)cbuf).ClearColor.Select(x=>(uint)x).ToArray ()); break;
                        case PixelFormat.RG16: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<float>)cbuf).ClearColor); break;
                        case PixelFormat.RG16f: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<float>)cbuf).ClearColor); break;
                        case PixelFormat.RG16i: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<short>)cbuf).ClearColor.Select(x=>(int)x).ToArray ()); break;
                        case PixelFormat.RG16ui: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<ushort>)cbuf).ClearColor.Select(x=>(uint)x).ToArray ()); break;
                        case PixelFormat.RG32f: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<float>)cbuf).ClearColor); break;
                        case PixelFormat.RG32i: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<int>)cbuf).ClearColor); break;
                        case PixelFormat.RG32ui: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<uint>)cbuf).ClearColor); break;

                        case PixelFormat.RGB8: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<float>)cbuf).ClearColor); break;
                        case PixelFormat.RGB8i: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<sbyte>)cbuf).ClearColor.Select(x=>(int)x).ToArray ()); break;
                        case PixelFormat.RGB8ui: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<byte>)cbuf).ClearColor.Select (x => (uint)x).ToArray ()); break;
                        case PixelFormat.RGB16: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<float>)cbuf).ClearColor); break;
                        case PixelFormat.RGB16f: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<float>)cbuf).ClearColor); break;
                        case PixelFormat.RGB16i: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<short>)cbuf).ClearColor.Select (x => (int)x).ToArray ()); break;
                        case PixelFormat.RGB16ui: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<ushort>)cbuf).ClearColor.Select (x => (uint)x).ToArray ()); break;
                        case PixelFormat.RGB32f: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<float>)cbuf).ClearColor); break;
                        case PixelFormat.RGB32i: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<int>)cbuf).ClearColor); break;
                        case PixelFormat.RGB32ui: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<uint>)cbuf).ClearColor); break;

                        case PixelFormat.RGBA8: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<float>)cbuf).ClearColor); break;
                        case PixelFormat.RGBA8i: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<sbyte>)cbuf).ClearColor.Select (x => (int)x).ToArray ()); break;
                        case PixelFormat.RGBA8ui: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<byte>)cbuf).ClearColor.Select (x => (uint)x).ToArray ()); break;
                        case PixelFormat.RGBA16: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<float>)cbuf).ClearColor); break;
                        case PixelFormat.RGBA16f: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<float>)cbuf).ClearColor); break;
                        case PixelFormat.RGBA16i: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<short>)cbuf).ClearColor.Select (x => (int)x).ToArray ()); break;
                        case PixelFormat.RGBA16ui: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<ushort>)cbuf).ClearColor.Select (x => (uint)x).ToArray ()); break;
                        case PixelFormat.RGBA32f: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<float>)cbuf).ClearColor); break;
                        case PixelFormat.RGBA32i: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<int>)cbuf).ClearColor); break;
                        case PixelFormat.RGBA32ui: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<uint>)cbuf).ClearColor); break;

                        case PixelFormat.RGB9e5: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<float>)cbuf).ClearColor); break;
                        case PixelFormat.R11G11B10f: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<float>)cbuf).ClearColor); break;
                        case PixelFormat.RGB10A2: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<float>)cbuf).ClearColor); break;
                        case PixelFormat.RGB10A2ui: GL.ClearBuffer (ClearBuffer.Color, i, ((Image2D<ushort>)cbuf).ClearColor.Select (x => (uint)x).ToArray()); break;
                        default: throw new NotImplementedException ("Sorry");
                    }
                }
            }

            foreach (var zbuf in fbuf.DepthBuffers) {
                var i = 0;
                switch (zbuf.PixelFormat) {
                    case PixelFormat.Depth: GL.ClearBuffer (ClearBuffer.Depth, 0, ((Image2D<float>)zbuf).ClearColor); break;
                    default: throw new NotImplementedException ("Sorry");
                }
            }

            // ステンシルは省略
        }

        /// <summary>
        /// フレームバッファーの完全性を検証します
        /// </summary>
        /// <remarks>
        /// フレーフバッファーが完全でない場合例外を発生します。
        /// </remarks>
        /// <param name="fbuf"><see cref="FrameBuffer"/>オブジェクト</param>
        public static void Validate (this FrameBuffer fbuf) {
            var cache = Graphics3DExtensions.GetGLCache ();
            var id = cache[fbuf];
            if (id == 0) {
                throw new InvalidOperationException ("OpenGL id was not found.");
            }

            GL.BindFramebuffer (FramebufferTarget.DrawFramebuffer, id);

            var status = GL.CheckFramebufferStatus (FramebufferTarget.DrawFramebuffer);
            if (status != FramebufferErrorCode.FramebufferComplete) {
                throw new InvalidOperationException ("FrameBuffer is invalid, status=" + status);
            }

            GL.BindFramebuffer (FramebufferTarget.DrawFramebuffer, 0);
        }
    }
}
